package cn.lyd.base.interceptor;


import cn.lyd.base.annotation.PreAuthorize;
import cn.lyd.base.jwt.JwtUtils;
import cn.lyd.base.jwt.Payload;
import cn.lyd.base.jwt.RsaUtils;
import cn.lyd.base.jwt.UserInfo;
import cn.lyd.base.until.LoginContext;
import cn.lyd.base.until.ThreadLocalUtil;
import cn.lyd.system.domain.Menu;
import cn.lyd.system.mapper.PermissionMapper;
import cn.lyd.user.domain.Logininfo;
import cn.lyd.user.domain.User;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.security.PublicKey;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
* @Description: 登录拦截器
* @Author: Mr.She
* @Version: 1.0
* @Date:  2022/6/13 16:54
*/
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private PermissionMapper permissionMapper;

    //获取yml配置中的jwt公钥-用来解密
    @Value("${jwt.lyd.pub}")
    private String pub;


    /**
     * redis方法解密
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    /*@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("44444444444444");

        // 3.如果能走到这里，说明token为空或者是Redis中用户信息为空，那么以未登录进行处理
        // 3.1.设置编码字符集
        response.setCharacterEncoding("UTF-8");
        // 3.2.设置响应参数类型为JSON
        response.setContentType("application/json;charset=UTF-8");

        // 从header中取出来token
        String token = request.getHeader("token");
        if (StringUtils.isNotBlank(token)){
            // 如果token存在
            Logininfo logininfo = (Logininfo) redisTemplate.opsForValue().get(token);
            if (null != logininfo){
                //如果是后台人员，才涉及到权限
                if(logininfo.getType() == 0){
                    //先查询当前人拥有的权限按钮
                   List<String> snList =  permissionMapper.findByLogininfoId(logininfo.getId());
                    //我们可以获取到当前他请求的controller的方法上的 自定义注解里的sn
                    HandlerMethod handlerMethod = (HandlerMethod) handler;
                    //handlerMethod能获取到当前请求的方法上的自定义注解
                    PreAuthorize preAuthorize = handlerMethod.getMethodAnnotation(PreAuthorize.class);
                    if (preAuthorize!=null){
                        String sn = preAuthorize.sn();
                        //判断 当前人拥有的权限 是否包含这个sn，如果不包含 就拦截 返回noPermission
                        if (!snList.contains(sn)){//当前集合是否包含这个字符串
                            // 3.3.获取到打印流,并设置响应数据
                            PrintWriter writer = response.getWriter();
                            writer.write("{\"success\":false,\"resultObj\":\"noPermission\"}");
                            // 3.4.刷新、关流
                            writer.flush();
                            writer.close();
                            return false;
                        }
                    }
                }
                redisTemplate.expire(token,30, TimeUnit.MINUTES);
                return true;
            }
        }
        // 3.3.获取到打印流,并设置响应数据
        PrintWriter writer = response.getWriter();
        writer.write("{\"success\":false,\"resultObj\":\"noLogin\"}");
        // 3.4.刷新、关流
        writer.flush();
        writer.close();
        return false;
    }
    */


    /**
     * jwt方法解密
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("44444444444444");

        // 3.如果能走到这里，说明token为空或者是Redis中用户信息为空，那么以未登录进行处理
        // 3.1.设置编码字符集
        response.setCharacterEncoding("UTF-8");
        // 3.2.设置响应参数类型为JSON
        response.setContentType("application/json;charset=UTF-8");
        log.info("555555555555");
        // 从header中取出来token
        String token = request.getHeader("token");
        //如果token存在
        if (StringUtils.isNotBlank(token)){
            // 2 解析token里面内容
            //拿到公钥
            PublicKey publicKey = RsaUtils.getPublicKey(JwtUtils.class.getClassLoader().getResource(pub).getFile());
            //拿到载荷
            Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, publicKey, UserInfo.class);
            //如果载荷不为空，去拿用户信息
            if (payload != null) {
                //把当前登录人的信息放到本地线程工具类中
                ThreadLocalUtil.setUserInfo(payload.getUserInfo());
                //拿用户信息
                UserInfo userInfo = payload.getUserInfo();
                Logininfo logininfo = userInfo.getLogininfo();
                List<Menu> menus = userInfo.getMenus();
                List<String> permissions = userInfo.getPermissions();
                //如果用户信息不为空,判断是不是后台人员0
                if (null != logininfo) {
                    //如果是后台人员，就有权限和菜单
                    if (logininfo.getType() == 0) {
                        //我们可以获取到当前他请求的controller的方法上的 自定义注解里的sn
                        HandlerMethod handlerMethod = (HandlerMethod) handler;
                        //handlerMethod能获取到当前请求的方法上的自定义注解
                        PreAuthorize preAuthorize = handlerMethod.getMethodAnnotation(PreAuthorize.class);
                        //如果有自定义注解=不是公共资源，去拿它   上面已经拿到了 userInfo.getPermissions();
                        if (preAuthorize != null) {
                            //拿到自定义注解的sn字段值
                            String sn = preAuthorize.sn();
                            //如果登录人的权限按钮里不包含这个sn=没有这个权限->拦截 包含就放行
                            if (!permissions.contains(sn)) {
                                // 3.3.获取到打印流,并设置响应数据
                                PrintWriter writer = response.getWriter();
                                writer.write("{\"success\":false,\"resultObj\":\"noPermission\"}");
                                // 3.4.刷新、关流
                                writer.flush();
                                writer.close();
                                return false;
                            }
                            //包含就放行,并刷新过期时间【用户30分钟无操作才过期】
                            // redis刷新过期时间
                            // redisTemplate.expire(token,30, TimeUnit.MINUTES);
                            //todo 此处应该刷新JWT过期时间，暂时不做，因为jwt目前是没有这个刷新过期时间的接口
                        }
                        //没有自定义注解，放行    公共资源不加自定义注解，所有内部资源才加自定义注解
                        return true;
                    }
                    //如果是前台用户，已经判断用户信息不为空-放行
                    return true;
                }
                //如果用户信息为空，拦截
            }
        }
        // 3.3.获取到打印流,并设置响应数据
        PrintWriter writer = response.getWriter();
        writer.write("{\"success\":false,\"resultObj\":\"noLogin\"}");
        // 3.4.刷新、关流
        writer.flush();
        writer.close();
        return false;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ThreadLocalUtil.remove();
    }
}
